/**********************************************************************

  Audacity: A Digital Audio Editor

  TimerRecordDialog.cpp

  Copyright 2006-2009 by Vaughan Johnson

  This program is free software; you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation; either version 2 of the License, or
  (at your option) any later version.

*******************************************************************//**

\class TimerRecordDialog
\brief Dialog for Timer Record, i.e., timed or long recording.

*//*******************************************************************/

#include "TimerRecordDialog.h"

#include "FileNames.h"

#include <thread>
#include <wx/setup.h> // for wxUSE_* macros

#include <wx/app.h>
#include <wx/wxcrtvararg.h>
#include <wx/button.h>
#include <wx/calctrl.h>
#include <wx/checkbox.h>
#include <wx/choice.h>
#include <wx/defs.h>
#include <wx/dir.h>
#include <wx/datectrl.h>
#include <wx/datetime.h>
#include <wx/dynlib.h> //<! For windows.h
#include <wx/frame.h>

#include "AudioIO.h"
#include "SelectFile.h"
#include "ShuttleGui.h"
#include "ProjectAudioManager.h"
#include "ProjectFileIO.h"
#include "ProjectFileManager.h"
#include "ProjectManager.h"
#include "ProjectRate.h"
#include "ProjectWindows.h"
#include "Project.h"
#include "Prefs.h"
#include "Track.h"
#include "TagsEditor.h"
#include "widgets/NumericTextCtrl.h"
#include "HelpSystem.h"
#include "AudacityMessageBox.h"
#include "ExportPluginRegistry.h"
#include "ExportUtils.h"
#include "ProgressDialog.h"
#include "wxTextCtrlWrapper.h"

#include "prefs/ImportExportPrefs.h"

#include "TimerRecordExportDialog.h"
#include "ExportProgressUI.h"

#if wxUSE_ACCESSIBILITY
#include "WindowAccessible.h"
#endif

#define TIMER_ID 7000

enum { // control IDs
    ID_DATEPICKER_START = 10000,
    ID_TIMETEXT_START,
    ID_DATEPICKER_END,
    ID_TIMETEXT_END,
    ID_TIMETEXT_DURATION,
    ID_AUTOSAVEPATH_BUTTON,
    ID_AUTOSAVEPATH_TEXT,
    ID_AUTOEXPORTPATH_BUTTON,
    ID_AUTOEXPORTPATH_TEXT,
    ID_AUTOSAVE_CHECKBOX,
    ID_AUTOEXPORT_CHECKBOX
};

enum {
    CONTROL_GROUP_SAVE,
    CONTROL_GROUP_EXPORT
};

// The slow timer interval is used to update the start and end times, which only show
// time to the nearest second.  So we only need an update once a second.
const int kSlowTimerInterval = 1000; // ms

// This timer interval is used in some busy-wait loops and is much shorter.
constexpr auto kTimerInterval = std::chrono::milliseconds{ 50 };

static double wxDateTime_to_AudacityTime(wxDateTime& dateTime)
{
    return (dateTime.GetHour() * 3600.0) + (dateTime.GetMinute() * 60.0) + dateTime.GetSecond();
}

namespace {
StringSetting DefaultExportAudioFormat{ L"/TimerRecordDialog/ExportFormat", L"WAV" };
StringSetting DefaultExportAudioPath{ L"/TimerRecordDialog/ExportPath", L"" };
}

// The purpose of the DatePickerCtrlAx class is to make to wxDatePickerCtrl more accessible for
// the NVDA screen reader.
// By default the msaa state of wxDatePickerCtrl is always normal (0x0), and this causes nvda not
// to read the control when the user tabs to it. This class
// modifies the state to be focusable + focused (when it's the focus).
// Note that even with this class NVDA still doesn't read the NEW selected part of the control when left/right
// arrow keys are used.

#if wxUSE_ACCESSIBILITY

class DatePickerCtrlAx final : public WindowAccessible
{
public:
    DatePickerCtrlAx(wxDatePickerCtrl* ctrl)
        : WindowAccessible(ctrl), mCtrl(ctrl) {}

    virtual ~DatePickerCtrlAx() {}

    // Returns a state constant.
    wxAccStatus GetState(int childId, long* state) override;

private:
    wxDatePickerCtrl* mCtrl;
};

// Returns a state constant.
wxAccStatus DatePickerCtrlAx::GetState(int WXUNUSED(childId), long* state)
{
    *state = wxACC_STATE_SYSTEM_FOCUSABLE;
    *state |= (mCtrl == wxWindow::FindFocus() ? wxACC_STATE_SYSTEM_FOCUSED : 0);

    return wxACC_OK;
}

#endif // wxUSE_ACCESSIBILITY

BEGIN_EVENT_TABLE(TimerRecordDialog, wxDialogWrapper)
EVT_DATE_CHANGED(ID_DATEPICKER_START, TimerRecordDialog::OnDatePicker_Start)
EVT_TEXT(ID_TIMETEXT_START, TimerRecordDialog::OnTimeText_Start)

EVT_DATE_CHANGED(ID_DATEPICKER_END, TimerRecordDialog::OnDatePicker_End)
EVT_TEXT(ID_TIMETEXT_END, TimerRecordDialog::OnTimeText_End)

EVT_TEXT(ID_TIMETEXT_DURATION, TimerRecordDialog::OnTimeText_Duration)

EVT_BUTTON(wxID_OK, TimerRecordDialog::OnOK)
EVT_BUTTON(wxID_HELP, TimerRecordDialog::OnHelpButtonClick)

EVT_TIMER(TIMER_ID, TimerRecordDialog::OnTimer)

EVT_BUTTON(ID_AUTOSAVEPATH_BUTTON, TimerRecordDialog::OnAutoSavePathButton_Click)
EVT_BUTTON(ID_AUTOEXPORTPATH_BUTTON, TimerRecordDialog::OnAutoExportPathButton_Click)

EVT_CHECKBOX(ID_AUTOSAVE_CHECKBOX, TimerRecordDialog::OnAutoSaveCheckBox_Change)
EVT_CHECKBOX(ID_AUTOEXPORT_CHECKBOX, TimerRecordDialog::OnAutoExportCheckBox_Change)

END_EVENT_TABLE()

TimerRecordDialog::TimerRecordDialog(
    wxWindow* parent, AudacityProject& project, bool bAlreadySaved)
    : wxDialogWrapper(parent, -1, XO("Audacity Timer Record"), wxDefaultPosition,
                      wxDefaultSize, wxCAPTION)
    , mProject{project}
{
    SetName();

    m_DateTime_Start = wxDateTime::UNow();
    long seconds; // default duration is 1 hour = 3600 seconds
    gPrefs->Read(wxT("/TimerRecord/LastDuration"), &seconds, 3600L);
    m_TimeSpan_Duration = wxTimeSpan::Seconds(seconds);
    m_DateTime_End = m_DateTime_Start + m_TimeSpan_Duration;

    m_pDatePickerCtrl_Start = NULL;
    m_pTimeTextCtrl_Start = NULL;

    m_pDatePickerCtrl_End = NULL;
    m_pTimeTextCtrl_End = NULL;

    m_pTimeTextCtrl_Duration = NULL;

    // Do we allow the user to change the Automatic Save file?
    m_bProjectAlreadySaved = bAlreadySaved;

    wxString exportPath;
    DefaultExportAudioPath.Read(&exportPath);
    if (exportPath.empty()) {
        exportPath = FileNames::FindDefaultPath(FileNames::Operation::Export);
    }
    m_fnAutoExportFile.SetPath(exportPath);

    m_fnAutoExportFile.SetName(mProject.GetProjectName());
    if (m_fnAutoExportFile.GetName().IsEmpty()) {
        m_fnAutoExportFile.SetName(_("untitled"));
    }

    DefaultExportAudioFormat.Read(&m_sAutoExportFormat);
    if (!m_sAutoExportFormat.empty()) {
        auto [plugin, formatIndex]
            = ExportPluginRegistry::Get().FindFormat(m_sAutoExportFormat);

        if (plugin != nullptr) {
            const auto formatInfo = plugin->GetFormatInfo(formatIndex);
            m_fnAutoExportFile.SetExt(formatInfo.extensions[0]);
        }
    }

    m_iAutoExportSampleRate = ProjectRate::Get(mProject).GetRate();
    m_iAutoExportChannels = AudioIORecordChannels.Read();

    ShuttleGui S(this, eIsCreating);
    this->PopulateOrExchange(S);

    // Set initial focus to "1" of "01h" in Duration NumericTextCtrl,
    // instead of OK button (default).
    m_pTimeTextCtrl_Duration->SetFocus();
    m_pTimeTextCtrl_Duration->SetFieldFocus(3);

    m_timer.SetOwner(this, TIMER_ID);
    m_timer.Start(kSlowTimerInterval);
}

TimerRecordDialog::~TimerRecordDialog() = default;

void TimerRecordDialog::OnTimer(wxTimerEvent& WXUNUSED(event))
{
    wxDateTime dateTime_UNow = wxDateTime::UNow();
    if (m_DateTime_Start < dateTime_UNow) {
        m_DateTime_Start = dateTime_UNow;
        m_pDatePickerCtrl_Start->SetValue(m_DateTime_Start);
        m_pTimeTextCtrl_Start->SetValue(wxDateTime_to_AudacityTime(m_DateTime_Start));
        this->UpdateEnd(); // Keep Duration constant and update End for changed Start.
    }
}

void TimerRecordDialog::OnDatePicker_Start(wxDateEvent& WXUNUSED(event))
{
    m_DateTime_Start = m_pDatePickerCtrl_Start->GetValue();
    double dTime = m_pTimeTextCtrl_Start->GetValue();
    long hr = (long)(dTime / 3600.0);
    long min = (long)((dTime - (hr * 3600.0)) / 60.0);
    long sec = (long)(dTime - (hr * 3600.0) - (min * 60.0));
    m_DateTime_Start.SetHour(hr);
    m_DateTime_Start.SetMinute(min);
    m_DateTime_Start.SetSecond(sec);

    // User might have had the dialog up for a while, or
    // had a future day, set hour of day less than now's, then changed day to today.
    wxTimerEvent dummyTimerEvent;
    this->OnTimer(dummyTimerEvent);

    // Always update End for changed Start, keeping Duration constant.
    // Note that OnTimer sometimes calls UpdateEnd, so sometimes this is redundant,
    // but OnTimer doesn't need to always call UpdateEnd, but we must here.
    this->UpdateEnd();
}

void TimerRecordDialog::OnTimeText_Start(wxCommandEvent& WXUNUSED(event))
{
    //v NumericTextCtrl doesn't implement upper ranges, i.e.,
    // if I tell it "024 h 060 m 060 s", then
    // user increments the hours past 23, it rolls over to 0
    // (although if you increment below 0, it stays at 0).
    // So instead, set the max to 99 and just catch hours > 24 and fix the ctrls.
    double dTime = m_pTimeTextCtrl_Start->GetValue();
    long days = (long)(dTime / (24.0 * 3600.0));
    if (days > 0) {
        dTime -= (double)days * 24.0 * 3600.0;
        m_DateTime_Start += wxTimeSpan::Days(days);
        m_pDatePickerCtrl_Start->SetValue(m_DateTime_Start);
        m_pTimeTextCtrl_Start->SetValue(dTime);
    }

    wxDateEvent dummyDateEvent;
    this->OnDatePicker_Start(dummyDateEvent);
}

void TimerRecordDialog::OnDatePicker_End(wxDateEvent& WXUNUSED(event))
{
    m_DateTime_End = m_pDatePickerCtrl_End->GetValue();
    double dTime = m_pTimeTextCtrl_End->GetValue();
    long hr = (long)(dTime / 3600.0);
    long min = (long)((dTime - (hr * 3600.0)) / 60.0);
    long sec = (long)(dTime - (hr * 3600.0) - (min * 60.0));
    m_DateTime_End.SetHour(hr);
    m_DateTime_End.SetMinute(min);
    m_DateTime_End.SetSecond(sec);

    // DatePickerCtrls use SetRange to make sure End is never less than Start, but
    // need to implement it for the TimeTextCtrls.
    if (m_DateTime_End < m_DateTime_Start) {
        m_DateTime_End = m_DateTime_Start;
        m_pDatePickerCtrl_End->SetValue(m_DateTime_End);
        m_pTimeTextCtrl_End->SetValue(wxDateTime_to_AudacityTime(m_DateTime_End));
    }

    this->UpdateDuration(); // Keep Start constant and update Duration for changed End.
}

void TimerRecordDialog::OnTimeText_End(wxCommandEvent& WXUNUSED(event))
{
    //v NumericTextCtrl doesn't implement upper ranges, i.e.,
    // if I tell it "024 h 060 m 060 s", then
    // user increments the hours past 23, it rolls over to 0
    // (although if you increment below 0, it stays at 0).
    // So instead, set the max to 99 and just catch hours > 24 and fix the ctrls.
    double dTime = m_pTimeTextCtrl_End->GetValue();
    long days = (long)(dTime / (24.0 * 3600.0));
    if (days > 0) {
        dTime -= (double)days * 24.0 * 3600.0;
        m_DateTime_End += wxTimeSpan::Days(days);
        m_pDatePickerCtrl_End->SetValue(m_DateTime_End);
        m_pTimeTextCtrl_End->SetValue(dTime);
    }

    wxDateEvent dummyDateEvent;
    this->OnDatePicker_End(dummyDateEvent);
}

void TimerRecordDialog::OnTimeText_Duration(wxCommandEvent& WXUNUSED(event))
{
    double dTime = m_pTimeTextCtrl_Duration->GetValue();
    long hr = (long)(dTime / 3600.0);
    long min = (long)((dTime - (hr * 3600.0)) / 60.0);
    long sec = (long)(dTime - (hr * 3600.0) - (min * 60.0));
    m_TimeSpan_Duration = wxTimeSpan(hr, min, sec); //v milliseconds?

    this->UpdateEnd(); // Keep Start constant and update End for changed Duration.
}

// New events for timer recording automation
void TimerRecordDialog::OnAutoSavePathButton_Click(wxCommandEvent& WXUNUSED(event))
{
    auto& projectFileIO = ProjectFileIO::Get(mProject);

    wxString fName = SelectFile(FileNames::Operation::Export,
                                XO("Save Timer Recording As"),
                                m_fnAutoSaveFile.GetPath(),
                                m_fnAutoSaveFile.GetFullName(),
                                wxT("aup3"),
                                { FileNames::AudacityProjects },
                                wxFD_SAVE | wxRESIZE_BORDER,
                                this);

    if (fName.empty()) {
        return;
    }

    // If project already exists then abort - we do not allow users to overwrite an existing project
    // unless it is the current project.
    if (wxFileExists(fName) && (projectFileIO.GetFileName() != fName)) {
        AudacityMessageDialog m(
            nullptr,
            XO(
                "The selected file name could not be used\nfor Timer Recording because it \
would overwrite another project.\nPlease try again and select an original name."),
            XO("Error Saving Timer Recording Project"),
            wxOK | wxICON_ERROR);
        m.ShowModal();
        return;
    }

    // Set this boolean to false so we now do a SaveAs at the end of the recording
    // unless we're saving the current project.
    m_bProjectAlreadySaved = projectFileIO.GetFileName() == fName ? true : false;

    m_fnAutoSaveFile = fName;
    m_fnAutoSaveFile.SetExt(wxT("aup3"));
    this->UpdateTextBoxControls();
}

void TimerRecordDialog::OnAutoExportPathButton_Click(wxCommandEvent& WXUNUSED(event))
{
    // Set the options required
    TimerRecordExportDialog exportDialog(mProject, this);
    exportDialog.Bind(
        m_fnAutoExportFile,
        m_sAutoExportFormat,
        m_iAutoExportSampleRate,
        m_iAutoExportChannels,
        m_AutoExportParameters);

    if (exportDialog.ShowModal() != wxID_OK) {
        return;
    }

    m_pTimerExportPathTextCtrl->SetValue(m_fnAutoExportFile.GetFullPath());

    // Update the text controls
    this->UpdateTextBoxControls();
}

void TimerRecordDialog::OnAutoSaveCheckBox_Change(wxCommandEvent& WXUNUSED(event))
{
    EnableDisableAutoControls(m_pTimerAutoSaveCheckBoxCtrl->GetValue(), CONTROL_GROUP_SAVE);
}

void TimerRecordDialog::OnAutoExportCheckBox_Change(wxCommandEvent& WXUNUSED(event))
{
    EnableDisableAutoControls(m_pTimerAutoExportCheckBoxCtrl->GetValue(), CONTROL_GROUP_EXPORT);
}

void TimerRecordDialog::OnHelpButtonClick(wxCommandEvent& WXUNUSED(event))
{
    HelpSystem::ShowHelp(this, L"Timer_Record", true);
}

void TimerRecordDialog::OnOK(wxCommandEvent& WXUNUSED(event))
{
    this->TransferDataFromWindow();
    if (!m_TimeSpan_Duration.IsPositive()) {
        AudacityMessageBox(
            XO("Duration is zero. Nothing will be recorded."),
            XO("Error in Duration"),
            wxICON_EXCLAMATION | wxOK);
        return;
    }

    // Validate that we have a Save and/or Export path setup if the appropriate check box is ticked
    wxString sTemp = m_fnAutoSaveFile.GetFullPath();
    if (m_pTimerAutoSaveCheckBoxCtrl->IsChecked()) {
        if (!m_fnAutoSaveFile.IsOk() || m_fnAutoSaveFile.IsDir()) {
            AudacityMessageBox(
                XO("Automatic Save path is invalid."),
                XO("Error in Automatic Save"),
                wxICON_EXCLAMATION | wxOK);
            return;
        }
    }
    if (m_pTimerAutoExportCheckBoxCtrl->IsChecked()) {
        if (!m_fnAutoExportFile.IsOk() || m_fnAutoExportFile.IsDir()) {
            AudacityMessageBox(
                XO("Automatic Export path is invalid."),
                XO("Error in Automatic Export"),
                wxICON_EXCLAMATION | wxOK);
            return;
        }
    }

    // MY: Estimate here if we have enough disk space to
    // complete this Timer Recording.
    // If we don't think there is enough space then ask the user
    // if they want to continue.
    // We don't stop the user from starting the recording
    // as its possible that they plan to free up some
    // space before the recording begins
    auto& projectManager = ProjectManager::Get(mProject);

    // How many minutes do we have left on the disc?
    int iMinsLeft = projectManager.GetEstimatedRecordingMinsLeftOnDisk();

    // How many minutes will this recording require?
    int iMinsRecording = m_TimeSpan_Duration.GetMinutes();

    // Do we have enough space?
    if (iMinsRecording >= iMinsLeft) {
        // Format the strings
        auto sRemainingTime = projectManager.GetHoursMinsString(iMinsLeft);
        auto sPlannedTime = projectManager.GetHoursMinsString(iMinsRecording);

        // Create the message string
        auto sMessage = XO(
            "You may not have enough free disk space to complete this Timer Recording, based on your current settings.\n\nDo you wish to continue?\n\nPlanned recording duration:   %s\nRecording time remaining on disk:   %s")
                        .Format(sPlannedTime, sRemainingTime);

        AudacityMessageDialog dlgMessage(
            nullptr,
            sMessage,
            XO("Timer Recording Disk Space Warning"),
            wxYES_NO | wxNO_DEFAULT | wxICON_WARNING);
        if (dlgMessage.ShowModal() != wxID_YES) {
            // User decided not to continue - bail out!
            return;
        }
    }

    m_timer.Stop(); // Don't need to keep updating m_DateTime_Start to prevent backdating.
    this->EndModal(wxID_OK);
    wxLongLong duration = m_TimeSpan_Duration.GetSeconds();
    // this will assert if the duration won't fit in a long
    gPrefs->Write(wxT("/TimerRecord/LastDuration"), duration.ToLong());
    gPrefs->Flush();
}

void TimerRecordDialog::EnableDisableAutoControls(bool bEnable, int iControlGoup)
{
    if (iControlGoup == CONTROL_GROUP_EXPORT) {
        m_pTimerExportPathTextCtrl->Enable(bEnable);
        m_pTimerExportPathButtonCtrl->Enable(bEnable);
    } else if (iControlGoup == CONTROL_GROUP_SAVE) {
        m_pTimerSavePathTextCtrl->Enable(bEnable);
        m_pTimerSavePathButtonCtrl->Enable(bEnable);
    }

    // Enable or disable the Choice box - if there is no Save or Export then this will be disabled
    if (m_pTimerAutoSaveCheckBoxCtrl->GetValue() || m_pTimerAutoExportCheckBoxCtrl->GetValue()) {
        m_pTimerAfterCompleteChoiceCtrl->Enable();
    } else {
        m_pTimerAfterCompleteChoiceCtrl->SetSelection(POST_TIMER_RECORD_NOTHING);
        m_pTimerAfterCompleteChoiceCtrl->Disable();
    }
}

void TimerRecordDialog::UpdateTextBoxControls()
{
    // Will update the text box controls
    m_pTimerSavePathTextCtrl->SetValue(m_fnAutoSaveFile.GetFullPath());
    m_pTimerExportPathTextCtrl->SetValue(m_fnAutoExportFile.GetFullPath());

    // MY: Ensure we still display "Current Project" if this has already been saved
    if (m_bProjectAlreadySaved) {
        m_pTimerSavePathTextCtrl->SetValue(_("Current Project"));
    }
}

/// Runs the wait for start dialog.  Returns -1 if the user clicks stop while we are recording
/// or if the post recording actions fail.
int TimerRecordDialog::RunWaitDialog()
{
    auto updateResult = ProgressResult::Success;

    const auto gAudioIO = AudioIO::Get();
    gAudioIO->DelayActions(true);
    {
        auto cleanup = finally([gAudioIO]{ gAudioIO->DelayActions(false); });

        if (m_DateTime_Start > wxDateTime::UNow()) {
            updateResult = this->WaitForStart();
        }

        if (updateResult != ProgressResult::Success) {
            // Don't proceed, but don't treat it as canceled recording. User just canceled waiting.
            return POST_TIMER_RECORD_CANCEL_WAIT;
        } else {
            // Record for specified time.
            ProjectAudioManager::Get(mProject).OnRecord(false);
            bool bIsRecording = true;

            auto sPostAction = Verbatim(
                m_pTimerAfterCompleteChoiceCtrl->GetStringSelection());

            // Two column layout.
            TimerProgressDialog::MessageTable columns{
                {
                    XO("Recording start:"),
                    XO("Duration:"),
                    XO("Recording end:"),
                    {},
                    XO("Automatic Save enabled:"),
                    XO("Automatic Export enabled:"),
                    XO("Action after Timer Recording:"),
                },
                {
                    GetDisplayDate(m_DateTime_Start),
                    Verbatim(m_TimeSpan_Duration.Format()),
                    GetDisplayDate(m_DateTime_End),
                    {},
                    (m_bAutoSaveEnabled ? XO("Yes") : XO("No")),
                    (m_bAutoExportEnabled ? XO("Yes") : XO("No")),
                    sPostAction,
                }
            };

            TimerProgressDialog
                progress(m_TimeSpan_Duration.GetMilliseconds().GetValue(),
                         XO("Audacity Timer Record Progress"),
                         columns,
                         pdlgHideCancelButton | pdlgConfirmStopCancel);

            // Make sure that start and end time are updated, so we always get the full
            // duration, even if there's some delay getting here.
            wxTimerEvent dummyTimerEvent;
            this->OnTimer(dummyTimerEvent);

            // Loop for progress display during recording.
            while (bIsRecording && (updateResult == ProgressResult::Success)) {
                updateResult = progress.UpdateProgress();
                using namespace std::chrono;
                std::this_thread::sleep_for(kTimerInterval);
                bIsRecording = (wxDateTime::UNow() <= m_DateTime_End); // Call UNow() again for extra accuracy...
            }
        }
    }

    // Must do this AFTER the timer project dialog has been deleted to ensure the application
    // responds to the AUDIOIO events...see not about bug #334 in the ProgressDialog constructor.
    ProjectAudioManager::Get(mProject).Stop();

    // Let the caller handle cancellation or failure from recording progress.
    if (updateResult == ProgressResult::Cancelled || updateResult == ProgressResult::Failed) {
        return POST_TIMER_RECORD_CANCEL;
    }

    return ExecutePostRecordActions((updateResult == ProgressResult::Stopped));
}

int TimerRecordDialog::ExecutePostRecordActions(bool bWasStopped)
{
    // MY: We no longer automatically (and silently) call ->Save() when the
    // timer recording is completed.  We can now Save and/or Export depending
    // on the options selected by the user.
    // Once completed, we can also close Audacity, restart the system or
    // shutdown the system.
    // If there was any error with the auto save or export then we will not do
    // the actions requested and instead present an error mesasge to the user.
    // Finally, if there is no post-record action selected then we output
    // a dialog detailing what has been carried out instead.

    bool bSaveOK = false;
    bool bExportOK = false;
    int iPostRecordAction = m_pTimerAfterCompleteChoiceCtrl->GetSelection();
    int iOverriddenAction = iPostRecordAction;
    bool bErrorOverride = false;

    // Do Automatic Save?
    if (m_bAutoSaveEnabled) {
        auto& projectFileManager = ProjectFileManager::Get(mProject);
        // MY: If this project has already been saved then simply execute a Save here
        if (m_bProjectAlreadySaved) {
            bSaveOK = projectFileManager.Save();
        } else {
            bSaveOK = projectFileManager.SaveFromTimerRecording(m_fnAutoSaveFile);
        }
    }

    // Do Automatic Export?
    if (m_bAutoExportEnabled) {
        const auto& tracks = TrackList::Get(mProject);
        if (ExportUtils::FindExportWaveTracks(tracks, false).empty()) {
            ShowExportErrorDialog(XO("All audio is muted."), XO("Warning"), false);
            bExportOK = true;
        } else {
            const auto t0 = std::max(.0, tracks.GetStartTime());

            auto [exportPlugin, formatIndex]
                =ExportPluginRegistry::Get().FindFormat(m_sAutoExportFormat);

            if (exportPlugin != nullptr) {
                auto builder = ExportTaskBuilder {}
                .SetFileName(m_fnAutoExportFile)
                .SetParameters(m_AutoExportParameters)
                .SetRange(t0, tracks.GetEndTime(), false)
                .SetSampleRate(m_iAutoExportSampleRate)
                .SetNumChannels(m_iAutoExportChannels)
                .SetPlugin(exportPlugin, formatIndex);

                ExportProgressUI::ExceptionWrappedCall([&]
                {
                    const auto result = ExportProgressUI::Show(builder.Build(mProject));
                    bExportOK = result == ExportResult::Success
                                || result == ExportResult::Stopped;
                });

                if (bExportOK) {
                    DefaultExportAudioPath.Write(m_fnAutoExportFile.GetPath());
                    DefaultExportAudioFormat.Write(m_sAutoExportFormat);
                }
            }
        }
    }

    // Check if we need to override the post recording action
    bErrorOverride = ((m_bAutoSaveEnabled && !bSaveOK) || (m_bAutoExportEnabled && !bExportOK));
    if (bErrorOverride || bWasStopped) {
        iPostRecordAction = POST_TIMER_RECORD_NOTHING;
    }

    if (iPostRecordAction == POST_TIMER_RECORD_NOTHING) {
        // If there is no post-record action then we can show a message indicating what has been done

        auto sMessage = (bWasStopped ? XO("Timer Recording stopped.")
                         : XO("Timer Recording completed."));

        if (m_bAutoSaveEnabled) {
            if (bSaveOK) {
                sMessage = XO("%s\n\nRecording saved: %s").Format(
                    sMessage, m_fnAutoSaveFile.GetFullPath());
            } else {
                sMessage = XO("%s\n\nError saving recording.").Format(sMessage);
            }
        }
        if (m_bAutoExportEnabled) {
            if (bExportOK) {
                sMessage = XO("%s\n\nRecording exported: %s").Format(
                    sMessage, m_fnAutoExportFile.GetFullPath());
            } else {
                sMessage = XO("%s\n\nError exporting recording.").Format(sMessage);
            }
        }

        if (bErrorOverride) {
            if ((iOverriddenAction != iPostRecordAction)
                && (iOverriddenAction != POST_TIMER_RECORD_NOTHING)) {
                // Inform the user that we have overridden the selected action
                sMessage = XO("%s\n\n'%s' has been canceled due to the error(s) noted above.").Format(
                    sMessage,
                    m_pTimerAfterCompleteChoiceCtrl->GetString(iOverriddenAction));
            }

            // Show Error Message Box
            AudacityMessageBox(
                sMessage,
                XO("Error"),
                wxICON_EXCLAMATION | wxOK);
        } else {
            if (bWasStopped && (iOverriddenAction != POST_TIMER_RECORD_NOTHING)) {
                sMessage = XO("%s\n\n'%s' has been canceled as the recording was stopped.").Format(
                    sMessage,
                    m_pTimerAfterCompleteChoiceCtrl->GetString(iOverriddenAction));
            }

            AudacityMessageBox(
                sMessage,
                XO("Timer Recording"),
                wxICON_INFORMATION | wxOK);
        }
    }

    // MY: Lets do some actions that only apply to Exit/Restart/Shutdown
    if (iPostRecordAction >= POST_TIMER_RECORD_CLOSE) {
        do {
            // Set the flags as appropriate based on what we have done
            wxUint32 eActionFlags = TR_ACTION_NOTHING;
            if (m_bAutoSaveEnabled && bSaveOK) {
                eActionFlags |= TR_ACTION_SAVED;
            }
            if (m_bAutoExportEnabled && bExportOK) {
                eActionFlags |= TR_ACTION_EXPORTED;
            }

            // Lets show a warning dialog telling the user what is about to happen.
            // If the user no longer wants to carry out this action then they can click
            // Cancel and we will do POST_TIMER_RECORD_NOTHING instead.
            auto iDelayOutcome = PreActionDelay(iPostRecordAction, (TimerRecordCompletedActions)eActionFlags);
            if (iDelayOutcome != ProgressResult::Success) {
                // Cancel the action!
                iPostRecordAction = POST_TIMER_RECORD_NOTHING;
                break;
            }
        } while (false);
    }

    // Return the action as required
    return iPostRecordAction;
}

TranslatableString TimerRecordDialog::GetDisplayDate(wxDateTime& dt)
{
#if defined(__WXMSW__)
    // On Windows, wxWidgets uses the system date control and it displays the
    // date based on the Windows locale selected by the user.  But, wxDateTime
    // using the strftime function to return the formatted date.  Since the
    // default locale for the Windows CRT environment is "C", the dates come
    // back in a different format.
    //
    // So, we make direct Windows calls to format the date like it the date
    // control.
    //
    // (Most of this taken from src/msw/datectrl.cpp)

    const wxDateTime::Tm tm(dt.GetTm());
    SYSTEMTIME st;
    wxString s;
    int len;

    st.wYear = (WXWORD)tm.year;
    st.wMonth = (WXWORD)(tm.mon - wxDateTime::Jan + 1);
    st.wDay = tm.mday;
    st.wDayOfWeek = st.wMinute = st.wSecond = st.wMilliseconds = 0;

    len = ::GetDateFormat(LOCALE_USER_DEFAULT,
                          DATE_SHORTDATE,
                          &st,
                          NULL,
                          NULL,
                          0);
    if (len > 0) {
        len = ::GetDateFormat(LOCALE_USER_DEFAULT,
                              DATE_SHORTDATE,
                              &st,
                              NULL,
                              wxStringBuffer(s, len),
                              len);
        if (len > 0) {
            s += wxT(" ") + dt.FormatTime();
            return Verbatim(s);
        }
    }
#endif

    // Use default formatting
    wxPrintf(wxT("%s\n"), dt.Format());
    return Verbatim(dt.FormatDate() + wxT(" ") + dt.FormatTime());
}

wxTextCtrlWrapper* TimerRecordDialog::NewPathControl(
    wxWindow* wParent, const int iID,
    const TranslatableString& sCaption, const TranslatableString& sValue)
{
    wxTextCtrlWrapper* pTextCtrl;
    wxASSERT(wParent); // to justify safenew
    pTextCtrl = safenew wxTextCtrlWrapper(wParent, iID, sValue.Translation());
    pTextCtrl->SetName(sCaption.Translation());
    return pTextCtrl;
}

void TimerRecordDialog::PopulateOrExchange(ShuttleGui& S)
{
    bool bAutoSave = gPrefs->ReadBool("/TimerRecord/AutoSave", false);
    bool bAutoExport = gPrefs->ReadBool("/TimerRecord/AutoExport", false);
    int iPostTimerRecordAction = gPrefs->ReadLong("/TimerRecord/PostAction", 0);

    S.SetBorder(5);
    using Options = NumericTextCtrl::Options;
    /* i18n-hint a format string for hours, minutes, and seconds */
    auto strFormat = XO("099 h 060 m 060 s");
    /* i18n-hint a format string for days, hours, minutes, and seconds */
    auto strFormat1 = XO("099 days 024 h 060 m 060 s");

    S.StartMultiColumn(2, wxCENTER);
    {
        S.StartVerticalLay(true);
        {
            /* i18n-hint: This string is used to configure the controls for times when the recording is
            * started and stopped. As such it is important that only the alphabetic parts of the string
            * are translated, with the numbers left exactly as they are.
            * The 'h' indicates the first number displayed is hours, the 'm' indicates the second number
            * displayed is minutes, and the 's' indicates that the third number displayed is seconds.
            */
            S.StartStatic(XO("Start Date and Time"), true);
            {
                m_pDatePickerCtrl_Start
                    =safenew wxDatePickerCtrl(S.GetParent(), // wxWindow *parent,
                                              ID_DATEPICKER_START, // wxWindowID id,
                                              m_DateTime_Start); // const wxDateTime& dt = wxDefaultDateTime,
                // const wxPoint& pos = wxDefaultPosition, const wxSize& size = wxDefaultSize, long style = wxDP_DEFAULT | wxDP_SHOWCENTURY, const wxValidator& validator = wxDefaultValidator, const wxString& name = "datectrl")
                m_pDatePickerCtrl_Start->SetRange(wxDateTime::Today(), wxInvalidDateTime); // No backdating.
#if wxUSE_ACCESSIBILITY
                m_pDatePickerCtrl_Start->SetAccessible(safenew DatePickerCtrlAx(m_pDatePickerCtrl_Start));
#endif
                S.Name(XO("Start Date"))
                .AddWindow(m_pDatePickerCtrl_Start);

                m_pTimeTextCtrl_Start = safenew NumericTextCtrl(FormatterContext::EmptyContext(),
                                                                S.GetParent(), ID_TIMETEXT_START, NumericConverterType_TIME(),
                                                                {}, 0,
                                                                Options{}
                                                                .MenuEnabled(false)
                                                                .CustomFormat(strFormat)
                                                                .Value(true, wxDateTime_to_AudacityTime(m_DateTime_Start)));
                S.Name(XO("Start Time"))
                .AddWindow(m_pTimeTextCtrl_Start);
            }
            S.EndStatic();

            S.StartStatic(XO("End Date and Time"), true);
            {
                m_pDatePickerCtrl_End
                    =safenew wxDatePickerCtrl(S.GetParent(), // wxWindow *parent,
                                              ID_DATEPICKER_END, // wxWindowID id,
                                              m_DateTime_End); // const wxDateTime& dt = wxDefaultDateTime,
                // const wxPoint& pos = wxDefaultPosition, const wxSize& size = wxDefaultSize,
                //                            long style = wxDP_DEFAULT | wxDP_SHOWCENTURY,
                //                            const wxValidator& validator = wxDefaultValidator,
                //                            const wxString& name = "datectrl")
                m_pDatePickerCtrl_End->SetRange(m_DateTime_Start, wxInvalidDateTime); // No backdating.
#if wxUSE_ACCESSIBILITY
                m_pDatePickerCtrl_End->SetAccessible(safenew DatePickerCtrlAx(m_pDatePickerCtrl_End));
#endif
                S.Name(XO("End Date"))
                .AddWindow(m_pDatePickerCtrl_End);

                m_pTimeTextCtrl_End = safenew NumericTextCtrl(FormatterContext::EmptyContext(),
                                                              S.GetParent(), ID_TIMETEXT_END, NumericConverterType_TIME(),
                                                              {}, 0,
                                                              Options{}
                                                              .MenuEnabled(false)
                                                              .CustomFormat(strFormat)
                                                              .Value(true, wxDateTime_to_AudacityTime(m_DateTime_End)));
                S.Name(XO("End Time"))
                .AddWindow(m_pTimeTextCtrl_End);
            }
            S.EndStatic();

            S.StartStatic(XO("Duration"), true);
            {
                m_pTimeTextCtrl_Duration = safenew NumericTextCtrl(FormatterContext::EmptyContext(),
                                                                   S.GetParent(), ID_TIMETEXT_DURATION, NumericConverterType_TIME(),
                                                                   {}, 0,
                                                                   Options{}
                                                                   .MenuEnabled(false)
                                                                   .CustomFormat(strFormat1)
                                                                   .Value(true, m_TimeSpan_Duration.GetSeconds().ToDouble()));
                /* i18n-hint: This string is used to configure the controls which shows the recording
                * duration. As such it is important that only the alphabetic parts of the string
                * are translated, with the numbers left exactly as they are.
                * The string 'days' indicates that the first number in the control will be the number of days,
                * then the 'h' indicates the second number displayed is hours, the 'm' indicates the third
                * number displayed is minutes, and the 's' indicates that the fourth number displayed is
                * seconds.
                */
                S.Name(XO("Duration"))
                .AddWindow(m_pTimeTextCtrl_Duration);
            }
            S.EndStatic();
        }
        S.EndVerticalLay();

        S.StartVerticalLay(true);
        {
            S.StartStatic(XO("Automatic Save"), true);
            {
                // If checked, the project will be saved when the recording is completed
                m_pTimerAutoSaveCheckBoxCtrl = S.Id(ID_AUTOSAVE_CHECKBOX).AddCheckBox(XXO("Enable &Automatic Save?"),
                                                                                      bAutoSave);
                S.StartMultiColumn(3, wxEXPAND);
                {
                    TranslatableString sInitialValue;
                    auto sSaveValue = ProjectFileIO::Get(mProject).GetFileName();
                    if (!sSaveValue.empty()) {
                        m_fnAutoSaveFile.Assign(sSaveValue);
                        sInitialValue = XO("Current Project");
                    }
                    S.AddPrompt(XXO("Save Project As:"));
                    m_pTimerSavePathTextCtrl = NewPathControl(
                        S.GetParent(), ID_AUTOSAVEPATH_TEXT,
                        XO("Save Project As:"), sInitialValue);
                    m_pTimerSavePathTextCtrl->SetReadOnly(true);
                    S.AddWindow(m_pTimerSavePathTextCtrl);
                    m_pTimerSavePathButtonCtrl = S.Id(ID_AUTOSAVEPATH_BUTTON).AddButton(XXO("Select..."));
                }
                S.EndMultiColumn();
            }
            S.EndStatic();

            S.StartStatic(XO("Automatic Export"), true);
            {
                m_pTimerAutoExportCheckBoxCtrl = S.Id(ID_AUTOEXPORT_CHECKBOX).AddCheckBox(XXO("Enable Automatic &Export?"), bAutoExport);
                S.StartMultiColumn(3, wxEXPAND);
                {
                    S.AddPrompt(XXO("Export Project As:"));
                    m_pTimerExportPathTextCtrl = NewPathControl(
                        S.GetParent(), ID_AUTOEXPORTPATH_TEXT,
                        XO("Export Project As:"), {});
                    m_pTimerExportPathTextCtrl->SetReadOnly(true);
                    m_pTimerExportPathTextCtrl->SetValue(m_fnAutoExportFile.GetFullPath());
                    S.AddWindow(m_pTimerExportPathTextCtrl);
                    m_pTimerExportPathButtonCtrl = S.Id(ID_AUTOEXPORTPATH_BUTTON).AddButton(XXO("Select..."));
                }
                S.EndMultiColumn();
            }
            S.EndStatic();

            S.StartStatic(XO("Options"), true);
            {
                S.StartMultiColumn(1, wxEXPAND);
                {
                    S.SetStretchyCol(0);
                    m_pTimerAfterCompleteChoiceCtrl = S.AddChoice(XXO("After Recording completes:"),
                    {
                        XO("Do nothing"),
                        XO("Exit Audacity"),
                  #ifdef __WINDOWS__
                        XO("Restart system"),
                        XO("Shutdown system"),
                  #endif
                    },
                                                                  iPostTimerRecordAction
                                                                  );
                }
                S.EndMultiColumn();
            }
            S.EndStatic();
        }
        S.EndVerticalLay();
    }
    S.EndMultiColumn();

    // MY: Added the help button here
    S.AddStandardButtons(eOkButton | eCancelButton | eHelpButton);

    Layout();
    Fit();
    SetMinSize(GetSize());
    Center();

    EnableDisableAutoControls(bAutoSave, CONTROL_GROUP_SAVE);
    EnableDisableAutoControls(bAutoExport, CONTROL_GROUP_EXPORT);
}

bool TimerRecordDialog::TransferDataFromWindow()
{
    double dTime;
    long hr;
    long min;
    long sec;

    m_DateTime_Start = m_pDatePickerCtrl_Start->GetValue();
    dTime = m_pTimeTextCtrl_Start->GetValue();
    hr = (long)(dTime / 3600.0);
    min = (long)((dTime - (hr * 3600.0)) / 60.0);
    sec = (long)(dTime - (hr * 3600.0) - (min * 60.0));
    m_DateTime_Start.SetHour(hr);
    m_DateTime_Start.SetMinute(min);
    m_DateTime_Start.SetSecond(sec);

    m_DateTime_End = m_pDatePickerCtrl_End->GetValue();
    dTime = m_pTimeTextCtrl_End->GetValue();
    hr = (long)(dTime / 3600.0);
    min = (long)((dTime - (hr * 3600.0)) / 60.0);
    sec = (long)(dTime - (hr * 3600.0) - (min * 60.0));
    m_DateTime_End.SetHour(hr);
    m_DateTime_End.SetMinute(min);
    m_DateTime_End.SetSecond(sec);

    m_TimeSpan_Duration = m_DateTime_End - m_DateTime_Start;

    // Pull the settings from the auto save/export controls and write to the pref file
    m_bAutoSaveEnabled = m_pTimerAutoSaveCheckBoxCtrl->GetValue();
    m_bAutoExportEnabled = m_pTimerAutoExportCheckBoxCtrl->GetValue();

    // MY: Obtain the index from the choice control so we can save to the prefs file
    int iPostRecordAction = m_pTimerAfterCompleteChoiceCtrl->GetSelection();

    // Save the options back to the prefs file
    gPrefs->Write("/TimerRecord/AutoSave", m_bAutoSaveEnabled);
    gPrefs->Write("/TimerRecord/AutoExport", m_bAutoExportEnabled);
    gPrefs->Write("/TimerRecord/PostAction", iPostRecordAction);

    return true;
}

// Update m_TimeSpan_Duration and ctrl based on m_DateTime_Start and m_DateTime_End.
void TimerRecordDialog::UpdateDuration()
{
    m_TimeSpan_Duration = m_DateTime_End - m_DateTime_Start;
    m_pTimeTextCtrl_Duration->SetValue(m_TimeSpan_Duration.GetSeconds().ToDouble());
}

// Update m_DateTime_End and ctrls based on m_DateTime_Start and m_TimeSpan_Duration.
void TimerRecordDialog::UpdateEnd()
{
    //v Use remaining disk -> record time calcs from AudacityProject::OnTimer to set range?
    m_DateTime_End = m_DateTime_Start + m_TimeSpan_Duration;
    //wxLogDebug( "Time start %s end %s",
    //   m_DateTime_Start.FormatISOCombined(' '),
    //   m_DateTime_End.FormatISOCombined(' ') );

    // Disable the range limitation (to fix Bug 1749 and 1978)
    // Otherwise SetVallue asserts when going back in time.
    m_pDatePickerCtrl_End->SetRange(wxInvalidDateTime, wxInvalidDateTime);
    m_pDatePickerCtrl_End->SetValue(m_DateTime_End);
    // Re-enable range limitation to constrain user input.
    m_pDatePickerCtrl_End->SetRange(m_DateTime_Start, wxInvalidDateTime); // No backdating.
    m_pDatePickerCtrl_End->Refresh();
    m_pTimeTextCtrl_End->SetValue(wxDateTime_to_AudacityTime(m_DateTime_End));
}

ProgressResult TimerRecordDialog::WaitForStart()
{
    // MY: The Waiting For Start dialog now shows what actions will occur after recording has completed
    auto sPostAction = Verbatim(
        m_pTimerAfterCompleteChoiceCtrl->GetStringSelection());

    // Two column layout.
    TimerProgressDialog::MessageTable columns{
        {
            XO("Waiting to start recording at:"),
            XO("Recording duration:"),
            XO("Scheduled to stop at:"),
            {},
            XO("Automatic Save enabled:"),
            XO("Automatic Export enabled:"),
            XO("Action after Timer Recording:"),
        },
        {
            GetDisplayDate(m_DateTime_Start),
            Verbatim(m_TimeSpan_Duration.Format()),
            GetDisplayDate(m_DateTime_End),
            {},
            (m_bAutoSaveEnabled ? XO("Yes") : XO("No")),
            (m_bAutoExportEnabled ? XO("Yes") : XO("No")),
            sPostAction,
        },
    };

    wxDateTime startWait_DateTime = wxDateTime::UNow();
    wxTimeSpan waitDuration = m_DateTime_Start - startWait_DateTime;
    TimerProgressDialog progress(waitDuration.GetMilliseconds().GetValue(),
                                 XO("Audacity Timer Record - Waiting for Start"),
                                 columns,
                                 pdlgHideStopButton | pdlgConfirmStopCancel | pdlgHideElapsedTime,
                                 /* i18n-hint: "in" means after a duration of time,
                                    which is shown below this string */
                                 XO("Recording will commence in:"));

    auto updateResult = ProgressResult::Success;
    bool bIsRecording = false;
    while (updateResult == ProgressResult::Success && !bIsRecording)
    {
        updateResult = progress.UpdateProgress();
        using namespace std::chrono;
        std::this_thread::sleep_for(kTimerInterval);
        bIsRecording = (m_DateTime_Start <= wxDateTime::UNow());
    }
    return updateResult;
}

ProgressResult TimerRecordDialog::PreActionDelay(int iActionIndex, TimerRecordCompletedActions eCompletedActions)
{
    auto sAction = Verbatim(m_pTimerAfterCompleteChoiceCtrl
                            ->GetString(iActionIndex));

    /* i18n-hint: %s is one of "Do nothing", "Exit Audacity", "Restart system",
       or "Shutdown system", and
       "in" means after a duration of time, shown below this string */
    auto sCountdownLabel = XO("%s in:").Format(sAction);

    // Two column layout.
    TimerProgressDialog::MessageTable columns{
        {
            XO("Timer Recording completed."),
            {},
            XO("Recording Saved:"),
            XO("Recording Exported:"),
            XO("Action after Timer Recording:"),
        },
        {
            {},
            {},
            ((eCompletedActions & TR_ACTION_SAVED) ? XO("Yes") : XO("No")),
            ((eCompletedActions & TR_ACTION_EXPORTED) ? XO("Yes") : XO("No")),
            sAction,
        },
    };

    wxDateTime dtNow = wxDateTime::UNow();
    wxTimeSpan tsWait = wxTimeSpan(0, 1, 0, 0);
    wxDateTime dtActionTime = dtNow.Add(tsWait);

    TimerProgressDialog dlgAction(tsWait.GetMilliseconds().GetValue(),
                                  XO("Audacity Timer Record - Waiting"),
                                  columns,
                                  pdlgHideStopButton | pdlgHideElapsedTime,
                                  sCountdownLabel);

    auto iUpdateResult = ProgressResult::Success;
    bool bIsTime = false;
    while (iUpdateResult == ProgressResult::Success && !bIsTime)
    {
        iUpdateResult = dlgAction.UpdateProgress();
        using namespace std::chrono;
        std::this_thread::sleep_for(kTimerInterval);
        bIsTime = (dtActionTime <= wxDateTime::UNow());
    }
    return iUpdateResult;
}

// Register a menu item

#include "CommandContext.h"
#include "MenuRegistry.h"
#include "CommonCommandFlags.h"
#include "Project.h"
#include "ProjectHistory.h"
#include "ProjectSettings.h"
#include "UndoManager.h"

namespace {
void OnTimerRecord(const CommandContext& context)
{
    auto& project = context.project;
    const auto& settings = ProjectSettings::Get(project);
    auto& undoManager = UndoManager::Get(project);
    auto& window = GetProjectFrame(project);

    // MY: Due to improvements in how Timer Recording saves and/or exports
    // it is now safer to disable Timer Recording when there is more than
    // one open project.
    if (AllProjects{}.size() > 1) {
        AudacityMessageBox(
            XO(
                "Timer Recording cannot be used with more than one open project.\n\nPlease close any additional projects and try again."),
            XO("Timer Recording"),
            wxICON_INFORMATION | wxOK);
        return;
    }

    // MY: If the project has unsaved changes then we no longer allow access
    // to Timer Recording.  This decision has been taken as the safest approach
    // preventing issues surrounding "dirty" projects when Automatic Save/Export
    // is used in Timer Recording.
    if ((undoManager.UnsavedChanges()) && (!TrackList::Get(project).empty())) {
        AudacityMessageBox(
            XO(
                "Timer Recording cannot be used while you have unsaved changes.\n\nPlease save or close this project and try again."),
            XO("Timer Recording"),
            wxICON_INFORMATION | wxOK);
        return;
    }

    // We check the selected tracks to see if there is enough of them to accommodate
    // all input channels and all of them have the same sampling rate.
    // Those checks will be later performed by recording function anyway,
    // but we want to warn the user about potential problems from the very start.
    const auto selectedTracks{ GetPropertiesOfSelected(project) };
    const int rateOfSelected{ selectedTracks.rateOfSelected };
    const bool anySelected{ selectedTracks.anySelected };
    const bool allSameRate{ selectedTracks.allSameRate };

    if (!allSameRate) {
        AudacityMessageBox(XO("The tracks selected "
                              "for recording must all have the same sampling rate"),
                           XO("Mismatched Sampling Rates"),
                           wxICON_ERROR | wxCENTRE);

        return;
    }

    // Only need the size
    const auto existingTracks
        =ProjectAudioManager::ChooseExistingRecordingTracks(project, true,
                                                            rateOfSelected);
    if (existingTracks.empty()) {
        if (anySelected && rateOfSelected
            != ProjectRate::Get(project).GetRate()) {
            AudacityMessageBox(XO(
                                   "Too few tracks are selected for recording at this sample rate.\n"
                                   "(Audacity requires two channels at the same sample rate for\n"
                                   "each stereo track)"),
                               XO("Too Few Compatible Tracks Selected"),
                               wxICON_ERROR | wxCENTRE);

            return;
        }
    }

    // We use this variable to display "Current Project" in the Timer Recording
    // save project field
    bool bProjectSaved = !ProjectFileIO::Get(project).IsModified();

    //we break the prompting and waiting dialogs into two sections
    //because they both give the user a chance to click cancel
    //and therefore remove the newly inserted track.

    TimerRecordDialog dialog(
        &window, project, bProjectSaved); /* parent, project, project saved? */
    int modalResult = dialog.ShowModal();
    if (modalResult == wxID_CANCEL) {
        // Cancelled before recording - don't need to do anything.
    } else {
        // Bug #2382
        // Allow recording to start at current cursor position.
      #if 0
        // Timer Record should not record into a selection.
        bool bPreferNewTrack;
        gPrefs->Read("/GUI/PreferNewTrackRecord", &bPreferNewTrack, false);
        if (bPreferNewTrack) {
            window.Rewind(false);
        } else {
            window.SkipEnd(false);
        }
      #endif

        int iTimerRecordingOutcome = dialog.RunWaitDialog();
        switch (iTimerRecordingOutcome) {
        case POST_TIMER_RECORD_CANCEL_WAIT:
            // Canceled on the wait dialog
            ProjectHistory::Get(project).RollbackState();
            break;
        case POST_TIMER_RECORD_CANCEL:
            // RunWaitDialog() shows the "wait for start" as well as "recording"
            // dialog if it returned POST_TIMER_RECORD_CANCEL it means the user
            // cancelled while the recording, so throw out the fresh track.
            // However, we can't undo it here because the PushState() is called in TrackPanel::OnTimer(),
            // which is blocked by this function.
            // so instead we mark a flag to undo it there.
            ProjectAudioManager::Get(project).SetTimerRecordCancelled();
            break;
        case POST_TIMER_RECORD_NOTHING:
            // No action required
            break;
        case POST_TIMER_RECORD_CLOSE:
            wxTheApp->CallAfter([]{
                // Simulate the application Exit menu item
                wxCommandEvent evt { wxEVT_MENU, wxID_EXIT };
                wxTheApp->AddPendingEvent(evt);
            });
            ProjectManager::Get(project).SetSkipSavePrompt(true);
            break;

#ifdef __WINDOWS__
        case POST_TIMER_RECORD_RESTART:
            // Restart System
            ProjectManager::Get(project).SetSkipSavePrompt(true);
            system("shutdown /r /f /t 30");
            break;
        case POST_TIMER_RECORD_SHUTDOWN:
            // Shutdown System
            ProjectManager::Get(project).SetSkipSavePrompt(true);
            system("shutdown /s /f /t 30");
            break;
#endif
        }
    }
}

const auto CanStopFlags = AudioIONotBusyFlag() | CanStopAudioStreamFlag();

using namespace MenuRegistry;
AttachedItem sAttachment{
    Command(wxT("TimerRecord"), XXO("&Timer Record..."),
            OnTimerRecord, CanStopFlags, wxT("Shift+T")),
    { wxT("Transport/Basic/Record"),
      { OrderingHint::After, wxT("Record2ndChoice") } }
};
}
